#include "TriangleApp.h"

#include <stdexcept>
#include <array>

TriangleApp::TriangleApp()
{
	loadModels();
	createPipelineLayout();
	recreateSwapChain();
	createCommandBuffers();
}

TriangleApp::~TriangleApp()
{
	vkDestroyPipelineLayout(lvDevice.device(), pipelineLayout, nullptr);
}

void TriangleApp::run()
{
	while (!lvWindow.shouldClose())
	{
		glfwPollEvents();
		drawFrame();
	}
	vkDeviceWaitIdle(lvDevice.device());
}

void TriangleApp::loadModels()
{
	std::vector<LvModel::Vertex> vertices{ 
		{{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
		{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
		{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
	};
	lvModel = std::make_unique<LvModel>(lvDevice, vertices);
}

void TriangleApp::createPipelineLayout()
{
	VkPushConstantRange pushConstantRange{};
	pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
	pushConstantRange.offset = 0;
	pushConstantRange.size = sizeof(SimplePushConstantData);

	VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
	pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
	pipelineLayoutInfo.setLayoutCount = 0;
	pipelineLayoutInfo.pSetLayouts = nullptr;
	pipelineLayoutInfo.pushConstantRangeCount = 1;
	pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
	if (vkCreatePipelineLayout(lvDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout))
	{
		throw std::runtime_error("failed to create pipeline layout.");
	}
}

void TriangleApp::createPipeline()
{
	assert(lvSwapChain != nullptr && "Cannot create pipeline before swap chain");
	assert(pipelineLayout != nullptr && "Cannot create pipeline before pipeline layout");

	PipelineConfigInfo pipelineConfig{};

	LvPipeline::defaultPipelineConfigInfo(pipelineConfig);

	pipelineConfig.renderPass = lvSwapChain->getRenderPass();
	pipelineConfig.pipelineLayout = pipelineLayout;

	lvPipeline = std::make_unique<LvPipeline>(
		lvDevice,
		"shaders/simple_shader.vert.spv",
		"shaders/simple_shader.frag.spv",
		pipelineConfig);
}

void TriangleApp::createCommandBuffers()
{
	commandBuffers.resize(lvSwapChain->imageCount());

	VkCommandBufferAllocateInfo alloInfo{};
	alloInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	alloInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
	alloInfo.commandPool = lvDevice.getCommandPool();
	alloInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
	if (vkAllocateCommandBuffers(lvDevice.device(), &alloInfo, commandBuffers.data()) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to allocate command buffers.");
	}
}

void TriangleApp::freeCommandBuffers()
{
	vkFreeCommandBuffers(lvDevice.device(), lvDevice.getCommandPool(),
		static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
	commandBuffers.clear();
}

void TriangleApp::drawFrame()
{
	uint32_t imageIndex;
	auto result = lvSwapChain->acquireNextImage(&imageIndex);

	if (result == VK_ERROR_OUT_OF_DATE_KHR)
	{
		recreateSwapChain();
		return;
	}
	else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
	{
		throw std::runtime_error("failed to acquire swap chain image");
	}

	recordCommandBuffer(imageIndex);
	result = lvSwapChain->submitCommandBuffers(&commandBuffers[imageIndex], &imageIndex);
	
	if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || lvWindow.wasWindowResizedFlag())
	{
		lvWindow.resetWindowResizedFlag();
		recreateSwapChain();
		return;
	}
	else if (result != VK_SUCCESS)
	{
		throw std::runtime_error("failed to present swap chain image!");
	}
}

void TriangleApp::recreateSwapChain()
{
	auto extent = lvWindow.getExtent();
	while (extent.width == 0 || extent.height == 0)
	{
		extent = lvWindow.getExtent();
		glfwWaitEvents();
	}
	vkDeviceWaitIdle(lvDevice.device());

	if (lvSwapChain == nullptr)
	{
		lvSwapChain = std::make_unique<LvSwapChain>(lvDevice, extent);
	}
	else
	{
		lvSwapChain = std::make_unique<LvSwapChain>(lvDevice, extent, std::move(lvSwapChain));
		if (lvSwapChain->imageCount() != commandBuffers.size())
		{
			freeCommandBuffers();
			createCommandBuffers();
		}
	}

	createPipeline();
}

void TriangleApp::recordCommandBuffer(uint32_t imageIndex)
{
	VkCommandBufferBeginInfo beginInfo{};
	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

	if (vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to begin recording command buffer!");
	}

	VkRenderPassBeginInfo renderPassInfo{};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.renderPass = lvSwapChain->getRenderPass();
	renderPassInfo.framebuffer = lvSwapChain->getFrameBuffer(imageIndex);
	renderPassInfo.renderArea.offset = { 0, 0 };
	renderPassInfo.renderArea.extent = lvSwapChain->getSwapChainExtent();

	std::array<VkClearValue, 2> clearValues{};
	clearValues[0].color = { 0.1f, 0.1f, 0.1f, 1.0f };
	clearValues[1].depthStencil = { 1.0f, 0 };
	renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
	renderPassInfo.pClearValues = clearValues.data();

	vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
	
	VkViewport viewport{};
	viewport.x = 0.0f;
	viewport.y = 0.0f;
	viewport.width = static_cast<float>(lvSwapChain->getSwapChainExtent().width);
	viewport.height = static_cast<float>(lvSwapChain->getSwapChainExtent().height);
	viewport.minDepth = 0.0f;
	viewport.maxDepth = 0.0f;
	VkRect2D scissor{ {0, 0}, lvSwapChain->getSwapChainExtent() };
	vkCmdSetViewport(commandBuffers[imageIndex], 0, 1, &viewport);
	vkCmdSetScissor(commandBuffers[imageIndex], 0, 1, &scissor);
	
	lvPipeline->bind(commandBuffers[imageIndex]);
	lvModel->bind(commandBuffers[imageIndex]);
	for (int i = 0; i < 4; i++)
	{
		SimplePushConstantData push{};
		push.offset = { 0.0f, 0.4f - i * 0.25f };
		push.color = { 0.0f, 0.0f, 0.2f + 0.2f * i };

		vkCmdPushConstants(
			commandBuffers[imageIndex],
			pipelineLayout,
			VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
			0, sizeof(SimplePushConstantData),
			&push);
		lvModel->draw(commandBuffers[imageIndex]);
	}


	vkCmdEndRenderPass(commandBuffers[imageIndex]);
	if (vkEndCommandBuffer(commandBuffers[imageIndex]) != VK_SUCCESS)
	{
		throw std::runtime_error("faild to record command buffer");
	}
}



